﻿using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Models;
using System;
using System.Text;

namespace SwaggerExtensions
{
    public static class SwaggerExtensions
    {
        public static IServiceCollection AddOpenApiService(this IServiceCollection services, IConfigurationSection section)
        {
            services.Configure<OpenApiConfig>(section);
            var config = section.Get<OpenApiConfig>() ?? new OpenApiConfig();
            if (!config.Enable) return services;
            if (config != null && config.Docs != null)
            {
                services.AddEndpointsApiExplorer();
                services.AddSwaggerGen(c =>
                {
                    foreach (var doc in config.Docs)
                    {
                        c.SwaggerDoc(doc.GroupName, new OpenApiInfo
                        {
                            
                            Version = doc.GroupName,
                            Title = config.Title,
                            Description = config.DocumentTitle,
                            TermsOfService = new Uri("https://www.xxx.com/"),//服务条款
                            Contact = new OpenApiContact
                            {
                                Name = "Li",
                                Email = string.Empty
                                //Url = new Uri("https://www.xxx.com/cms"),
                            },
                            License = new OpenApiLicense
                            {
                                Name = "License",
                                Url = new Uri("https://www.xxx.com/license"),
                            }
                        });
                    }

                    DirectoryInfo directoryInfo = new DirectoryInfo(AppContext.BaseDirectory);
                    //获取当前的目录的文件
                    FileInfo[] fileInfos = directoryInfo.GetFiles("*.xml");
                    foreach (FileInfo f in fileInfos)
                    {
                        c.IncludeXmlComments(f.FullName, true);
                    }

                    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
                    {
                        Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
                        Name = "Authorization",
                        In = ParameterLocation.Header,
                        Type = SecuritySchemeType.Http,
                        BearerFormat = "JWT",
                        Scheme = "Bearer"
                    });
                    c.UseAllOfToExtendReferenceSchemas();
                    c.SupportNonNullableReferenceTypes();
                    c.UseAllOfForInheritance();
                    c.SchemaFilter<DefaultValueSchemaFilter>();
                    c.SchemaFilter<RequiredNotNullableSchemaFilter>();
                    c.SchemaFilter<EnumSchemaFilter>();
                    c.OperationFilter<SwaggerOptionalFormDataFilter>();
                    c.ParameterFilter<ImplicitRequiredParameterFilter>();

                });
                services.AddSwaggerGenNewtonsoftSupport();
            }
            return services;
        }

        /// <summary>
        /// 使用OPENAPI
        /// </summary>
        /// <param name="app"></param>
        public static void UseOpenApiUI(this WebApplication app)
        {

            var config = app.Configuration.GetSection("OpenApi").Get<OpenApiConfig>() ?? new OpenApiConfig();

            if (!config.Enable) return;
            app.UseSwagger(c =>
            {
                // c.RouteTemplate = routePath + c.RouteTemplate;
                c.RouteTemplate = "/doc/{documentName}/swagger.json";
            });

            const string CustomStyles = @"
                .swagger-ui .opblock .opblock-summary .view-line-link {
         margin: 0 5px;
         width: 24px;
            }
            ";



            app.MapGet($"/{config.RoutePrefix}/custom.css", [ApiExplorerSettings(IgnoreApi = true)] () => Results.Text(CustomStyles, "text/css", Encoding.UTF8)).ExcludeFromDescription();

            app.UseSwaggerUI(options =>
            {
                options.RoutePrefix = config.RoutePrefix;
                options.InjectStylesheet($"/{config.RoutePrefix}/custom.css");

                if (config.Docs != null)
                {
                    foreach (var doc in config.Docs)
                    {
                        options.SwaggerEndpoint($"/doc/{doc.GroupName}/swagger.json", doc.GroupName);
                    }
                }
                options.DocExpansion(config.DocExpansion);//折叠Api
                options.DocumentTitle = config.DocumentTitle;
            });


        }


        /* public static IServiceCollection AddOpenApiService(this IServiceCollection services, IConfigurationSection section)
         {
             services.Configure<OpenApiConfig>(section);
             var config = section.Get<OpenApiConfig>() ?? new OpenApiConfig();
             if (!config.Enable) return services;

             #region Swagger 接口文档定义
             services.AddEndpointsApiExplorer();

             // 注册Swagger生成器，定义一个或多个Swagger文档
             services.AddSwaggerGen(c =>
             {
                 //c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
                 if (config != null && config.Docs != null)
                 {
                     foreach (var doc in config.Docs)
                     {
                         c.SwaggerDoc(doc.GroupName, new OpenApiInfo
                         {
                             Version = doc.GroupName,
                             Title = config.Title,
                             Description = config.DocumentTitle,
                             TermsOfService = new Uri("https://www.xxx.com/"),//服务条款
                             Contact = new OpenApiContact
                             {
                                 Name = "LI",
                                 Email = string.Empty
                                 //Url = new Uri("https://www.xxx.com/cms"),
                             },
                             License = new OpenApiLicense
                             {
                                 Name = "License",
                                 Url = new Uri("https://www.xxx.com/license"),
                             }
                         });
                     }
                 }

                 DirectoryInfo directoryInfo = new DirectoryInfo(AppContext.BaseDirectory);
                 //获取当前的目录的文件
                 FileInfo[] fileInfos = directoryInfo.GetFiles("*.xml");
                 foreach (FileInfo f in fileInfos)
                 {
                     c.IncludeXmlComments(f.FullName, true);
                 }


                 #region 为SwaggerUI添加全局token验证
                 c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
                 {
                     Description = "在下框中输入请求头中需要添加Jwt授权Token,格式：Bearer {Token}",
                     Name = "Authorization",
                     In = ParameterLocation.Header,
                     Type = SecuritySchemeType.ApiKey,
                     BearerFormat = "JWT",
                     Scheme = "Bearer"
                 });


                 #endregion
                 // c.IncludeXmlCommentsFromInheritDocs(includeRemarks: true, excludedTypes: typeof(string));

                 // use it if you want to hide Paths and Definitions from OpenApi documentation correctly
                 c.UseAllOfToExtendReferenceSchemas();

                 c.SupportNonNullableReferenceTypes();

                 c.UseAllOfForInheritance();
                 c.SchemaFilter<DefaultValueSchemaFilter>();

                 c.SchemaFilter<RequiredNotNullableSchemaFilter>();
                 c.SchemaFilter<EnumSchemaFilter>();
                 *//*  c.AddEnumsWithValuesFixFilters(o =>
                   {
                       // add schema filter to fix enums (add 'x-enumNames' for NSwag or its alias from XEnumNamesAlias) in schema
                       o.ApplySchemaFilter = true;

                       // alias for replacing 'x-enumNames' in swagger document
                       o.XEnumNamesAlias = "x-enum-varnames";

                       // alias for replacing 'x-enumDescriptions' in swagger document
                       o.XEnumDescriptionsAlias = "x-enum-descriptions";

                       // add parameter filter to fix enums (add 'x-enumNames' for NSwag or its alias from XEnumNamesAlias) in schema parameters
                       o.ApplyParameterFilter = true;

                       // add document filter to fix enums displaying in swagger document
                       o.ApplyDocumentFilter = true;

                       // add descriptions from DescriptionAttribute or xml-comments to fix enums (add 'x-enumDescriptions' or its alias from XEnumDescriptionsAlias for schema extensions) for applied filters
                       o.IncludeDescriptions = true;

                       // add remarks for descriptions from xml-comments
                       o.IncludeXEnumRemarks = true;

                       // get descriptions from DescriptionAttribute then from xml-comments
                       o.DescriptionSource = DescriptionSources.DescriptionAttributesThenXmlComments;

                       // get descriptions from xml-file comments on the specified path
                       // should use "options.IncludeXmlComments(xmlFilePath);" before

                       foreach (FileInfo f in fileInfos)
                       {
                           o.IncludeXmlCommentsFrom(f.FullName);
                           //  options.IncludeXmlComments(f.FullName, true);
                       }
                   });
   *//*
                 c.OperationFilter<SwaggerOptionalFormDataFilter>();
                 c.ParameterFilter<ImplicitRequiredParameterFilter>();

             });
             services.AddSwaggerGenNewtonsoftSupport();
             // services.Replace<ISwaggerProvider, CachingSwaggerProvider>();
             #endregion
             return services;
         }

         /// <summary>
         /// 
         /// </summary>
         /// <param name="app"></param>
         /// <param name="appConfig"></param>
         public static void UseOpenApiUI(this WebApplication app)
         {
             var config = app.Configuration.GetSection("OpenApi").Get<OpenApiConfig>() ?? new OpenApiConfig();

             if (!config.Enable) return;

             const string CustomStyles = @"
 .swagger-ui .opblock .opblock-summary .view-line-link {
     margin: 0 5px;
     width: 24px;
 }
 ";
             app.MapGet("/swagger/custom.css", [ApiExplorerSettings(IgnoreApi = true)] () => Results.Text(CustomStyles, "text/css", Encoding.UTF8)).ExcludeFromDescription();
             //  string ApiName = "CMSAdmin";
             //启用swagger中间件
             app.UseSwagger(c =>
             {
                 // c.RouteTemplate = routePath + c.RouteTemplate;
                 c.RouteTemplate = "/doc/{documentName}/swagger.json";
             });

             //启用中间件服务swagger-ui (HTML, JS, CSS等)，
             //指定Swagger JSON端点。
             app.UseSwaggerUI(c =>
             {
                 if (config.Docs != null)
                 {
                     foreach (var doc in config.Docs)
                     {
                         c.SwaggerEndpoint($"/doc/{doc.GroupName}/swagger.json", $"{doc.GroupName}");
                         //  options.SwaggerEndpoint($"/openapi/{doc.GroupName}.json", doc.GroupName);
                     }
                 }
                 c.RoutePrefix = config.RoutePrefix;
                 //   c.RoutePrefix = "api-doc";//直接根目录访问
                 c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);//折叠Api
                 c.InjectStylesheet("/swagger/custom.css");
             });
         }*/
    }

    /* /// <summary>
     /// Swagger文档枚举字段显示枚举属性和枚举值,以及枚举描述
     /// </summary>
     public class EnumSchemaFilter : ISchemaFilter
     {
         /// <summary>
         /// 实现接口
         /// </summary>
         /// <param name="model"></param>
         /// <param name="context"></param>

         public void Apply(OpenApiSchema model, SchemaFilterContext context)
         {
             if (context.Type.IsEnum)
             {
                 model.Enum.Clear();
                 Enum.GetNames(context.Type)
                .ToList()
                .ForEach(name =>
                {
                    Enum e = (Enum)Enum.Parse(context.Type, name);
                    //model.Enum.Add(new Open)
                    model.Description += $"\n\n{Convert.ToInt32(Enum.Parse(context.Type, name))} = {name} ({e.ToDescription()})";
                    model.ExternalDocs = new OpenApiExternalDocs() {   Description = model.Description };
                    model.Enum.Add(new OpenApiInteger(Convert.ToInt32(Enum.Parse(context.Type, name))));
                    model.Example = new OpenApiString($"{Convert.ToInt32(Enum.Parse(context.Type, name))}");
                });
             }
         }
     }


     public class ImplicitRequiredParameterFilter : IParameterFilter
     {
         public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
         {
             parameter.Required = context.ApiParameterDescription.ModelMetadata?.IsRequired ?? false;
         }
     }
     public class SwaggerOptionalFormDataFilter : IOperationFilter
     {
         public void Apply(OpenApiOperation operation, OperationFilterContext context)
         {
             if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor descriptor)
             {
                 // If not [AllowAnonymous] and [Authorize] on either the endpoint or the controller...
                 if (!context.ApiDescription.CustomAttributes().Any((a) => a is AllowAnonymousAttribute)
                     && (context.ApiDescription.CustomAttributes().Any((a) => a is AuthorizeAttribute)
                         || descriptor.ControllerTypeInfo.GetCustomAttribute<AuthorizeAttribute>() != null))
                 {
                     operation.Security.Add(new OpenApiSecurityRequirement
                     {
                         [new OpenApiSecurityScheme
                         {
                             Reference = new OpenApiReference
                             {
                                 Type = ReferenceType.SecurityScheme,
                                 Id = "Bearer",
                             },
                         }] = Array.Empty<string>()
                     });
                 }
             }

             if (operation.RequestBody?.Content.ContainsKey("multipart/form-data") ?? false)
             {
                 foreach (var content in operation.RequestBody.Content)
                 {
                     var requiredProps = content.Value.Schema.Required;
                     var props = content.Value.Schema.Properties;
                     foreach (var prop in props)
                     {
                         var paramterDescription = context.ApiDescription.ParameterDescriptions.FirstOrDefault(paramDesc => paramDesc.Name == prop.Key);
                         if (!requiredProps.Contains(prop.Key) && paramterDescription != null && paramterDescription.Type.IsReferenceOrNullableType())
                         {
                             prop.Value.Nullable = true;
                         }
                     }
                 }
             }
         }
     }

     class RequiredNotNullableSchemaFilter : ISchemaFilter
     {
         public void Apply(OpenApiSchema schema, SchemaFilterContext context)
         {
             if (schema.Properties == null)
             {
                 return;
             }

             FixNullableProperties(schema, context);
             *//*
                         var notNullableProperties = schema
                             .Properties
                             .Where(x => !x.Value.Nullable && !schema.Required.Contains(x.Key))
                             .ToList();*//*
             var notNullableProperties = schema
       .Properties
       .Where(x => !x.Value.Nullable && x.Value.Default == default && !schema.Required.Contains(x.Key))
       .ToList();
             foreach (var property in notNullableProperties)
             {
                 schema.Required.Add(property.Key);
             }
         }
         /// <summary>
         /// Option "SupportNonNullableReferenceTypes" not working with complex types ({ "type": "object" }), 
         /// so they always have "Nullable = false",
         /// see method "SchemaGenerator.GenerateSchemaForMember"
         /// </summary>
         private static void FixNullableProperties(OpenApiSchema schema, SchemaFilterContext context)
         {
             foreach (var property in schema.Properties)
             {
                 if (property.Value.Reference != null)
                 {
                     var field = context.Type
                         .GetMembers(BindingFlags.Public | BindingFlags.Instance)
                         .FirstOrDefault(x =>
                             string.Equals(x.Name, property.Key, StringComparison.InvariantCultureIgnoreCase));

                     if (field != null)
                     {
                         var fieldType = field switch
                         {
                             FieldInfo fieldInfo => fieldInfo.FieldType,
                             PropertyInfo propertyInfo => propertyInfo.PropertyType,
                             _ => throw new NotSupportedException(),
                         };

                         property.Value.Nullable = fieldType.IsValueType
                             ? Nullable.GetUnderlyingType(fieldType) != null
                             : !field.IsNonNullableReferenceType();
                     }
                 }
             }
         }
     }

     public class DefaultValueSchemaFilter : ISchemaFilter
     {
         bool IsNullableType(  Type that)
         {
             return !(that == null) && !that.IsArray && that.FullName.StartsWith("System.Nullable`1[");
         }
         public void Apply(OpenApiSchema schema, SchemaFilterContext context)
         {
             if (schema.Properties == null)
             {
                 return;
             }
             foreach (var propertyInfo in context.Type.GetProperties())
             {
                 var defaultValue = (propertyInfo.GetCustomAttribute<DefaultValueAttribute>())?.Value;
                 object? objvalue = null;
                 if (context.Type.IsClass && !context.Type.IsAbstract
                     && !context.Type.IsGenericType && !context.Type.IsArray
                     && context.Type != typeof(string) && context.Type.IsPublic)
                 {
                     try
                     {
                         var obj = Activator.CreateInstance(context.Type);

                         if (!IsNullableType(propertyInfo.PropertyType))
                         {
                             objvalue = propertyInfo.GetValue(obj);
                         }
                     }
                     catch (Exception ex)
                     {
                         Console.WriteLine(ex.ToString());
                     }
                 }

                 var val = defaultValue ?? objvalue;
                 if (val == null && propertyInfo.PropertyType != typeof(string))
                 {
                     continue;
                 }
                 //var val = defaultValue;
                 foreach (var (key, value) in schema.Properties)
                 {
                     if (propertyInfo.Name.ToLower().Equals(key.ToLower()))
                     {
                         try
                         {
                             if (val is int intValue)
                             {
                                 value.Example = new OpenApiInteger(intValue);
                                 break;
                             }
                             else if (val is string stringValue)
                             {
                                 value.Example = new OpenApiString(stringValue);
                                 break;
                             }

                             else if (val is DateTime dateTimeValue)
                             {
                                 if (dateTimeValue == default(DateTime))
                                 {
                                     dateTimeValue = DateTime.Now;
                                 }
                                 value.Example = new OpenApiDateTime(dateTimeValue);
                                 break;
                             }
                             else if (val is Boolean boolValue)
                             {
                                 value.Example = new OpenApiBoolean(boolValue);
                                 break;
                             }
                             else
                             {
                                 value.Example = OpenApiAnyFactory.CreateFromJson(JSON.Serialize(val));
                                 break;
                             }
                         }
                         catch (Exception ex)
                         {

                             Console.WriteLine($"Error: {ex.ToString()}");
                         }

                     }
                 }
             }
         }
     }

 */
}
